﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace CoreCommon
{
    /// <summary>
    /// Типы сообщений об ошибке: 
    /// <list type="bullet">
    /// <item><description>Message - сообещние о предвиденной ошибке в программе</description></item>
    /// <item><description>Assert - сообщение о невыполненных краевых условиях </description></item>
    /// <item><description>Critical - неперехваченная ошибка</description></item>
    /// </list>    
    /// </summary>
    public enum ErrType { 
        /// <summary>
        /// Неопределенный тип
        /// </summary>
        None, 
        /// <summary>
        /// Сообщение для пользователя
        /// </summary>
        Message, 
        /// <summary>
        /// Предупреждение
        /// </summary>
        Assert, 
        /// <summary>
        /// Критическая ошибка
        /// </summary>
        Critical, 
        /// <summary>
        /// Уведомление появляющееся в трее, необходимо для этого назначить сообщение
        /// </summary>
        Notify 
    };

    /// <summary>
    /// Класс содержит методы для централизованной обработки исключительных ситуаций
    /// </summary>
    public class CoreException : Exception
    {
        #region Constructors

        /// <summary>
        /// Создается класс исключительной ситуации с добавлением информации о типе ошибки и сообщения
        /// </summary>
        /// <param name="errType">Тип ошибки <see cref="ErrType"/></param>
        /// <param name="message">Текст ошибки</param>
        public CoreException(ErrType errType, string message) : base(message)
        {
            _message = message;
            _errType = errType;
        }

        /// <summary>
        /// Создается класс исключительной ситуации с добавлением информации о типе ошибки, сообщения и экзепляра предыдущей исключительной ситуации
        /// </summary>
        /// <param name="errType">Тип ошибки <see cref="ErrType"/></param>
        /// <param name="message">Текст ошибки</param>
        /// <param name="error">Предыдущая исключительная ситуация</param>
        public CoreException(ErrType errType, string message, Exception error) : base(message, error)
        {
            _errType = errType;
            _message = message;
            _exception = error;
        }
        
        /// <summary>
        /// Конструктор по умолчанию закрывается
        /// </summary>
        protected CoreException() : base() { }

        #endregion

        #region Публичные свойства и методы

        /// <summary>
        /// Сообщение об ошибке
        /// </summary>
        public string Msg
        {
            get
            {
                return _message;
            }
        }

        /// <summary>
        /// Экземпляр предыдущей исключительной ситуации
        /// </summary>
        public Exception Exp
        {
            get
            {
                return _exception;
            }
        }

        /// <summary>
        /// Тип ошибки
        /// </summary>
        public ErrType ErrorType
        {
            get
            {
                return _errType;
            }
        }

        /// <summary>
        /// Статическая функция для перехвата исключительной ситуации на самом верхнем уровне
        /// </summary>
        /// <param name="e">Класс исключительной ситуации</param>
        public static void HandleError(UnhandledExceptionEventArgs e)
        {
            EmptyErrorStackToLog();
        }

        /// <summary>
        /// Другая статическая функция для перехвата исключительной ситуации на самом верхнем уровне
        /// </summary>
        /// <param name="error">Класс исключительной ситуации</param>
        public static void HandleError(Exception error)
        {
            bool IsUriException = error.GetType() == typeof(CoreException);
            StringBuilder stringBuilder = new StringBuilder();

            if (IsUriException)
            {
                CoreException wfErr = (CoreException)error;
                switch (wfErr.ErrorType)
                {
                    case ErrType.Message:
                        ShowError(wfErr.Msg);
                        if (wfErr.Exp != null)
                        {
                            Log.ToLog(wfErr.Msg);
                            LevelExceptionToLog(wfErr, 0, stringBuilder);
                            Log.ToLog(stringBuilder.ToString());
                        }
                        break;
                    case ErrType.Critical:
                        ShowError(wfErr.Msg);
                        Log.ToLog(wfErr.Msg);
                        stringBuilder.AppendLine("***CRITICAL");
                        LevelExceptionToLog(wfErr, 0, stringBuilder);
                        Log.ToLog(stringBuilder.ToString());
                        break;
                    case ErrType.Assert:
                        Log.ToLog(wfErr.Msg);
                        stringBuilder.AppendLine("***ASSERT");
                        LevelExceptionToLog(wfErr, 0, stringBuilder);
                        Log.ToLog(stringBuilder.ToString());
                        break;
                    case ErrType.Notify:
                        stringBuilder.AppendLine("***NOTIFY");
                        Log.ToLog(wfErr.Msg);
                        OnCoreEvent(new CoreEventArgs(wfErr.Msg));
                        break;
                    case ErrType.None:
                    default:
                        break;
                }
            }
            else
            {
                Log.ToLog(error.Message);
                LevelExceptionToLog(error, 0, stringBuilder);
            }
        }

        #endregion

        #region Вспомогательные методы

        private static void ShowError(string data)
        {
            MessageBox.Show(null, data, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
      
        private static void LevelExceptionToLog(Exception e, int exceptionLevel, StringBuilder logString)
        {
            exceptionLevel++;
            object[] arrayErr = { exceptionLevel, e.GetType().Name.ToString(), e.HelpLink, e.Message, e.Source, e.StackTrace, e.TargetSite };
            logString.AppendLine(String.Format("***Exception Level: {0}", exceptionLevel));
            logString.AppendLine(String.Format("***ExceptionType: {0}",  e.GetType().Name.ToString()));
            logString.AppendLine(String.Format("***HelpLine: {0}", e.HelpLink));
            logString.AppendLine(String.Format("***Message: {0}", e.Message));
            logString.AppendLine(String.Format("***Source: {0}", e.Source));
            logString.AppendLine(String.Format("***StackTrace: {0}", e.StackTrace));
            logString.AppendLine(String.Format("***TargetSite: {0}", e.TargetSite));
            logString.Append(String.Format("***Data: {0}", e.Data));
            foreach (DictionaryEntry de in e.Data)
            {
                logString.AppendLine(String.Format(CultureInfo.InvariantCulture, "  {0} : {1}", de.Key, de.Value));
            }
            Log.ToLog(logString.ToString());

            Exception ie = e.InnerException;
            while (ie != null)
            {
                LevelExceptionToLog(ie, exceptionLevel++, logString);
                if (exceptionLevel > 1)
                    ie = ie.InnerException;
                else
                    ie = null;
            }
            exceptionLevel--;
        }
        
        private static void EmptyErrorStackToLog()
        {
            string currentStackTrace = System.Environment.StackTrace;
            string firstStackTraceCall = "System.Environment.get_StackTrace( )";

            int posOfStackTraceCall = currentStackTrace.IndexOf(firstStackTraceCall, StringComparison.Ordinal);
            string finalStackTrace = currentStackTrace.Substring(posOfStackTraceCall + firstStackTraceCall.Length);
            MatchCollection methodCallMatches = Regex.Matches(finalStackTrace, @"\sat\s.*(\sin\s.*\:line\s\d*)?");
            StringBuilder log = new StringBuilder("-------------");
            foreach (Match m in methodCallMatches)
            {
                log.Append(m.Value + System.Environment.NewLine + "-------------");
            }
        }

        #endregion

        #region Fields

        private Exception _exception; 
        private ErrType _errType;
        private string _message;

        #endregion

        #region Events

        /// <summary>
        /// Обработчик события отображения сообщения об ошибке в трее
        /// </summary>
        /// <param name="e"></param>
        protected static void OnCoreEvent(CoreEventArgs e)
        {
            EventHandler<CoreEventArgs> handler = CoreEvent;
            if (handler != null)
            {
                handler(CoreEvent, e);
            }
        }

        /// <summary>
        /// Событие для генерации сообщения об ошибке в трее
        /// </summary>
        public static event EventHandler<CoreEventArgs> CoreEvent;

        #endregion

    }

    /// <summary>
    /// Класс события для отображения сообщения об ошибке в трее
    /// </summary>
    public class CoreEventArgs : EventArgs
    {
        /// <summary>
        /// Конструктор по умолчанию
        /// </summary>
        /// <param name="text">Текст сообщения</param>
        public CoreEventArgs(string text)
        {
            msg = text;
            TimeReached = DateTime.Now;
        }
        /// <summary>
        /// Сообщения для вывода в трее
        /// </summary>
        public string msg { get; set; }
        /// <summary>
        /// Время генерации сообщения
        /// </summary>
        public DateTime TimeReached { get; set; }
    }

}
